﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CarbonExchange.AmeeQuery;
using CarbonExchange.Dal;
using System.Xml.Linq;
using CarbonExchange.Bll.Exceptions;

namespace CarbonExchange.Bll
{
    public class FactoryReadOnlyBase<TItem> where TItem : ItemBase
    {
        protected FactoryReadOnlyBase()
        {
            this.AmeeCommand = StructureMap.ObjectFactory.GetInstance<IAmeeCommand>();
            this.DalContext = StructureMap.ObjectFactory.GetInstance<Context>();
        }

        protected FactoryReadOnlyBase(IAmeeCommand ameeCommand)
        {
            this.AmeeCommand = ameeCommand;
            this.DalContext = StructureMap.ObjectFactory.GetInstance<Context>();
        }

        protected FactoryReadOnlyBase(Context dalContext)
        {
            this.AmeeCommand = StructureMap.ObjectFactory.GetInstance<IAmeeCommand>();
            this.DalContext = dalContext;
        }

        protected FactoryReadOnlyBase(IAmeeCommand ameeCommand, Context dalContext)
        {
            this.AmeeCommand = ameeCommand;
            this.DalContext = dalContext;
        }

        protected IAmeeCommand AmeeCommand { get; private set; }
        protected Context DalContext { get; private set; }
    }

    public abstract class FactoryBase<TItem> : FactoryReadOnlyBase<TItem> where TItem : ItemBase
    {
        protected FactoryBase() : base() { }

        protected FactoryBase(IAmeeCommand ameeCommand) : base(ameeCommand) { }

        protected FactoryBase(Context dalContext) : base(dalContext) { }

        protected FactoryBase(IAmeeCommand ameeCommand, Context dalContext) : base(ameeCommand, dalContext) { }

        public void Delete(TItem item)
        {
            Delete(new TItem[] { item }.AsQueryable());
        }

        public virtual void Delete(IQueryable<TItem> items)
        {
            foreach (TItem item in items)
            {
                MarkItemForDeletion(item);
            }
            DalContext.SubmitChanges();
        }

        public void Delete(IEnumerable<TItem> items)
        {
            Delete(items.AsQueryable());
        }

        protected abstract void MarkItemForDeletion(TItem item);

        public void Save(TItem item)
        {
            Save(new TItem[] { item }.AsQueryable());
        }

        public void Save(List<TItem> items)
        {
            Save(items.AsQueryable());
        }

        public void Save(IList<TItem> items)
        {
            Save(items.AsQueryable());
        }

        public virtual void Save(IQueryable<TItem> items)
        {
            ValidateItems(items);

            Dictionary<object, TItem> changes = new Dictionary<object, TItem>();
            foreach (TItem item in items)
            {
                object dataItem = GetDataItemForUpdate(item);
                changes.Add(dataItem, item);
            }

            foreach (var t2 in DalContext.GetChangeSet().Updates)
            {
                var s = t2.GetType();
            }

            try
            {
                DalContext.SubmitChanges();
            }
            catch(Exception e)
            {
                foreach(var t in DalContext.GetChangeSet().Updates)
                {
                    var s = t.GetType();
                }
            }
            //save back db created properties
            foreach (object dalItem in changes.Keys)
            {
                PopulateDataChanges(changes[dalItem], dalItem);
            }
        }

        public void ValidateItems(IQueryable<TItem> items)
        {
            ValidationException exp = new ValidationException();
            Validate(items, exp);
            if (exp.Items.Count() > 0)
            {
                throw exp;
            }
        }

        protected abstract void Validate(IQueryable<TItem> items, ValidationException exp);

        protected abstract void PopulateDataChanges(TItem item, object dataItem);

        protected abstract object GetDataItemForUpdate(TItem item);

    }

    public abstract class FactorySequenceBase<TItem> : FactoryBase <TItem> where TItem : SequenceItemBase
    {
        protected FactorySequenceBase()
            : base()
        {
        }

        protected FactorySequenceBase(IAmeeCommand ameeCommand)
            : base(ameeCommand)
        {
        }

        protected FactorySequenceBase(Context dalContext)
            : base(dalContext)
        {
        }

        protected FactorySequenceBase(IAmeeCommand ameeCommand, Context dalContext)
            : base(ameeCommand, dalContext)
        {
        }


        public void Move(IList<TItem> list, int from, int to)
        {
            if(list == null)
            {
                throw new ArgumentNullException("list");
            }
            if(from < 0 || from > list.Count - 1)
            {
                throw new IndexOutOfRangeException(string.Format("Index for 'from' argument ('{0}') is not within the bounds of the list containg '{1}' items.", from, list.Count()));
            }
            if(to < 0 || to > list.Count - 1)
            {
                throw new IndexOutOfRangeException(string.Format("Index for 'to' argument ('{0}') is not within the bounds of the list containg '{1}' items.", to, list.Count()));
            }

            TItem moving = list[from];
            list.Remove(moving);
            list.Insert(to, moving);

            for (int i = 0; i < list.Count; i++)
            {
                list[i].Sequence = i;
                SetDalSequence(list[i], i);
            }
            base.DalContext.SubmitChanges();
        }

        protected abstract void SetDalSequence(TItem item, int sequence);

        public void Swap(IList<TItem> list, int index, int with)
        {
            if(list == null)
            {
                throw new ArgumentNullException("list");
            }
            if(index < 0 || index > list.Count - 1)
            {
                throw new IndexOutOfRangeException(string.Format("Index for 'index' argument ('{0}') is not within the bounds of the list containg '{1}' items.", index, list.Count()));
            }
            if(with < 0 || with > list.Count - 1)
            {
                throw new IndexOutOfRangeException(string.Format("Index for 'with' argument ('{0}') is not within the bounds of the list containg '{1}' items.", with, list.Count()));
            }

            TItem moving = list[index];
            TItem swap = list[with];
            list[with]=moving;
            list[index]=swap;

            for (int i = 0; i < list.Count; i++)
            {
                list[i].Sequence = i;
                SetDalSequence(list[i], i);
            }
            base.DalContext.SubmitChanges();
        }

        public override void Save(IQueryable<TItem> items)
        {
            if (items.Count() > 0)
            {
                //check sequence is ok
                SetInitalSequence(items);
            }
            base.Save(items);
        }

        protected abstract void SetInitalSequence(IQueryable<TItem> items);
    }
}

